LambdaのDLQ(デッドレターキュー)とDestinations(非同期呼び出しの宛先指定)を比較してみた
非同期呼び出しのLambdaは、処理失敗時に他のAWSサービスを実行する方法として、DLQ(デッドレターキュー)と(失敗時の)Destinations(非同期呼び出しの宛先指定)があります。DLQは2016年12月にリリースされた機能、かたやDestinationsは2019年11月にリリースされた比較的新しい機能です。
この2つの機能の違い、またはどう使い分けるべきか調べてみました。実際に両機能を使ってみて、得られる情報の違いを比較します。
実装&テスト
Serverless Frameworkで実装します。前回のエントリで失敗時のDestinationsについては実装済なので、そこにDLQも追加し、関数実行してみます。
ハイライト部分がDLQとして追加した箇所です。
service: name: destinations-sample custom: webpack: webpackConfig: ./webpack.config.js includeModules: true fail-queue-name: fail-destination-queue-by-sls plugins: - serverless-webpack - serverless-pseudo-parameters provider: name: aws runtime: nodejs12.x region: ap-northeast-1 environment: AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1 iamRoleStatements: - Effect: Allow Action: sqs:SendMessage Resource: !GetAtt DLQ.Arn functions: hello: handler: handler.hello destinations: onFailure: arn:aws:sqs:#{AWS::Region}:#{AWS::AccountId}:${self:custom.fail-queue-name} onError: !GetAtt DLQ.Arn resources: Resources: FailQueue: Type: "AWS::SQS::Queue" Properties: QueueName: ${self:custom.fail-queue-name} MessageRetentionPeriod: 1209600 DLQ: Type: "AWS::SQS::Queue" Properties: QueueName: dlq-by-sls MessageRetentionPeriod: 1209600
Lambda関数は前回と同じです。イベントペイロードに{ "success": false }
を渡すとエラーを発生させるコードです。
import "source-map-support/register"; export const hello = async (event) => { console.log(event); if (event.success === false) { throw new Error("destination test"); } return true; };
関数実行します。{ "success": false }
を渡したので、再試行含め計3回実行した後、DLQに指定したdlq-by-slsキューとDestinationsに指定したfail-destination-queue-by-slsキューにそれぞれメッセージが送信されているはずです。
npx sls invoke -f hello -d '{ "success": false }' -t Event
メッセージ確認
DLQ
$ aws sqs receive-message \ > --queue-url https://sqs.ap-northeast-1.amazonaws.com/123456789012/dlq-by-sls \ > --attribute-names All \ > --message-attribute-names All \ > --max-number-of-messages 10
{ "Messages": [ { "MessageId": "14fe15eb-c097-4c59-82ae-b3812cfce169", "ReceiptHandle": "AQEBguEFsZi3HdPLKx1Wb0Ya946j6z3EhQsaDlwugvV6qZTn3MA6Xe+dFsRoiu/77C6iGhMDjfKDnTwKIPWXN5++lQm1XNv9/4qK9Tg/A7G62KgJF0HebToO0aP/kh1S8HF2FSv9r8c24OJsWkHlZnodf3YFQAY7lcq13+4F1y7iTgZtM08DhkWoHlNzsskfKChmjVgu8EkXO85TfOJd/1SjWPub/TSWlr819M2eGP5SWM4Do2abTNdC1sAUUKVnt3K58HqJFz5vv21a7OKs88SIbRBJoe10M8YckKumoKiTEN9v19UnLPW1ALr33RMvwwh4RuvHz8VVlxmdeW/tWXLIKWhJThMV5819BqhQAa0oJGoMe7QlyLQKAaKayKYIgT81ACE/yrKQrhls73HbD6Z5Dg==", "MD5OfBody": "c0a4229f65148628b26e451304ddac68", "Body": "{\"success\":false}", "Attributes": { "SenderId": "AROAQWCYJEBRJM6ORNHK5:awslambda_800_20200628065429546", "ApproximateFirstReceiveTimestamp": "1593327635445", "ApproximateReceiveCount": "1", "SentTimestamp": "1593327269630" }, "MD5OfMessageAttributes": "7df843cefe0509f157ec71d0877ad60d", "MessageAttributes": { "ErrorCode": { "StringValue": "200", "DataType": "Number" }, "ErrorMessage": { "StringValue": "destination test", "DataType": "String" }, "RequestID": { "StringValue": "b1084703-4304-4a53-ad7e-c1fa189ef921", "DataType": "String" } } } ] }
Destinations
$ aws sqs receive-message \ > --queue-url https://sqs.ap-northeast-1.amazonaws.com/123456789012/fail-destination-queue-by-sls \ > --attribute-names All \ > --message-attribute-names All \ > --max-number-of-messages 10 \
{ "Messages": [ { "MessageId": "24f74995-8de9-43bc-a832-46814d58e6d6", "ReceiptHandle": "AQEBFxDcx7eHBBzT+60qDt7jdAAyMTtM56agiXbi1i5ABvXJw3qG8/2We5Io5ev8586Lc0T1hkZ/PA1s1loz6+rNCGXjZ5EVD4YlaYnNSxKqskNI3p6XM86lXPn8RyeXL5YVDNo6NCiMaUq0BmifxUjnz1Py6oyR3cbd/USYuKwztBS6tBO7GJd/qZjSA4bhE8bP/zkPK7xhxxcG8mZ5Jodiq3xamGlCTlLLUGSqLarakJ0P6S/1L06aii5N7nZj4unmAilangnJF/9/tNdNcUDxIO9Jg5Jd6eW0EZn63h1fIlTdFOykDrJb3Q0W812NY0M2tMDfr+nvBKxHYc5Fm/10QzO53EYPFD4x0UlrOoN2WS9O7j6tBT3EfVB+uJruneOkMGwKlbFvoXEWLy3vgfqXq3tYDIoRC/kfmhbWPItnbDg=", "MD5OfBody": "70243e28b6f79f34491efd1abeba7752", "Body": "{\"version\":\"1.0\",\"timestamp\":\"2020-06-28T06:54:29.645Z\",\"requestContext\":{\"requestId\":\"b1084703-4304-4a53-ad7e-c1fa189ef921\",\"functionArn\":\"arn:aws:lambda:ap-northeast-1:123456789012:function:destinations-sample-dev-hello:$LATEST\",\"condition\":\"RetriesExhausted\",\"approximateInvokeCount\":3},\"requestPayload\":{\"success\":false},\"responseContext\":{\"statusCode\":200,\"executedVersion\":\"$LATEST\",\"functionError\":\"Unhandled\"},\"responsePayload\":{\"errorType\":\"Error\",\"errorMessage\":\"destination test\",\"trace\":[\"Error: destination test\",\" at Runtime.n [as handler] (/var/task/webpack:/handler.ts:6:15)\",\" at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)\"]}}", "Attributes": { "SenderId": "AROAQWCYJEBRJM6ORNHK5:awslambda_800_20200628065429546", "ApproximateFirstReceiveTimestamp": "1593327594714", "ApproximateReceiveCount": "2", "SentTimestamp": "1593327269660" } } ] }
Body部をパースしてみます。
$ aws sqs receive-message \ > --queue-url https://sqs.ap-northeast-1.amazonaws.com/123456789012/fail-destination-queue-by-sls \ > --attribute-names All \ > --message-attribute-names All \ > --max-number-of-messages 10 \ > | jq -r .Messages[0].Body \ > | jq
{ "version": "1.0", "timestamp": "2020-06-28T06:54:29.645Z", "requestContext": { "requestId": "b1084703-4304-4a53-ad7e-c1fa189ef921", "functionArn": "arn:aws:lambda:ap-northeast-1:123456789012:function:destinations-sample-dev-hello:$LATEST", "condition": "RetriesExhausted", "approximateInvokeCount": 3 }, "requestPayload": { "success": false }, "responseContext": { "statusCode": 200, "executedVersion": "$LATEST", "functionError": "Unhandled" }, "responsePayload": { "errorType": "Error", "errorMessage": "destination test", "trace": [ "Error: destination test", " at Runtime.n [as handler] (/var/task/webpack:/handler.ts:6:15)", " at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)" ] } }
比較
Destinationsの方が情報量が多い
DLQになくてDestinationsにある情報は以下です。
- errorタイプ
- Trace情報
- requestContext
- Lambda関数のARN
- condition - なぜ失敗判定されたか、ということかと思われます。今回の場合
RetriesExhausted
なので、規定のリトライ回数再試行したけど処理成功しなかったから、ということです。 - approximateInvokeCount - 処理試行回数
- responseContext
- ステータスコード
- Lambda関数のバージョン
- functionError
逆にDLQにある情報はすべてDestinationsに含まれています。
DLQはMessageAttributesにも情報がある
DLQはMessageAttributes(メッセージ属性)にも情報があります。というかBodyにはイベントペイロードしかないという潔さです。 Body内部ではなくMessageAttributesに情報があることで、若干目的の情報にたどり着くのが簡単になるでしょう。とくに以下のようにコンソールで確認する際は見やすいです。
リトライ処理はDLQの方が少しだけ簡単?
前述のとおりBodyにはイベントペイロードしか記載されていないので、それを使ってのLambda関数再実行はDLQの方が(やや)簡単に実装できるかと思います。以下例です。
$ npx sls invoke -f hello -d \ > "$(aws sqs receive-message --queue-url https://sqs.ap-northeast-1.amazonaws.com/123456789012/dlq-by-sls --max-number-of-messages 1\ > | jq -r .Messages[0].Body)" \ > -t Event
とはいえDestinationsもjqをもう一度かませばよいだけなので、これは本当に微々たる差ですね。。
$ npx sls invoke -f hello -d \ > "$(aws sqs receive-message --queue-url https://sqs.ap-northeast-1.amazonaws.com/123456789012/dlq-by-sls --max-number-of-messages 1\ > | jq -r .Messages[0].Body)\ > | jq -r .requestPayload)" \ > -t Event
と思ったのですが、Destinationsの方が簡単かもと思う部分も見つけました。
DLQのメッセージにはエラーが起きたLambda関数についての情報がありませんでした。ですので、どのLambda関数から送られてきたメッセージか、という部分はユーザー側で管理する必要がありそうです。大抵の場合SQSキュー名とLambda関数名を関連付けてわかりやすくされていると思いますが、そうしていない場合は辛そうです。
その他の違い
Destinationsの方が配信先のサービスが多い
DLQはSNSトピックと、SQSキューに対して配信可能です。一方のDestinationsはSNSトピックとSQSキューに加えLambda関数、EventBridgeにも配信が可能です。
バージョン
Lambda関数のバージョニングを使用する場合、DLQ、Destinations両方ともバージョンごとに設定が可能です。つまりバージョンが異なれば違う宛先に配信することができます。 しかし、DLQはバージョン毎に設定がロックされます。つまり過去バージョンのDLQの設定(宛先)を変更することはできません。一方Destinationsは変更可能です。
まとめ
DLQと(失敗時の)Destinationsについて比べてみました。情報量の多さ、連携できるAWSサービスの多さから、多くの場合でDestinationsを使うほうが良いかと思います。